package com.example.dmweb.electricity;

import com.example.dmcommom.Enum.ElectricityCategoryEnum;
import com.example.dmcommom.Enum.ElectricityTimeCategoryEnum;
import com.example.dmcommom.SpringBeanHelper;
import com.example.dmcommom.TimeUtil;
import com.example.dmdomain.ElectricityPriceRepository;
import com.example.dmdomain.ElectricityTimeSlotRepository;
import com.example.dmdomain.ProvinceRepository;
import com.example.dmdomain.entity.ElectricityPrice;
import com.example.dmdomain.entity.ElectricityTimeSlot;
import com.example.dmweb.electricity.model.EleNodeInfo;
import com.example.dmweb.electricity.model.ElectricityQueryResponse;
import com.example.dmweb.electricity.model.ElectricityQueryYearlyResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ElectricityInfoService {

	private static final String[] TYPE_LIST = new String[]{"city", "type1", "type2", "vol1", "vol2"};

	public ElectricityQueryResponse getPrice(String city, String type, String voltage, Integer year, Integer month) throws Exception {
		ElectricityTimeSlotRepository electricityTimeSlotRepository = SpringBeanHelper.getBean(ElectricityTimeSlotRepository.class);
		ElectricityPriceRepository electricityPriceRepository = SpringBeanHelper.getBean(ElectricityPriceRepository.class);
		List<String> types = Arrays.asList(type.split("_"));
		if (types.size() < 2) {
			throw new Exception("电力类型入参错误,请按照 (单一制/两部制)_(大工业/工商业) 的格式输入");
		}
		ElectricityQueryResponse response = new ElectricityQueryResponse();
		ProvinceRepository repository = SpringBeanHelper.getBeanOrThrow(ProvinceRepository.class);
		String province = repository.findProvinceByCity(city);
		response.setCity(city);
		response.setYear(year);
		response.setMonth(month);
		response.setVoltage(voltage);
		response.setElectricityCategory(type);

		Specification<ElectricityTimeSlot> timeSlotSpecification = (root, query, cb) -> {

			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			orList.add(cb.like(root.get("city"), city + "%"));
			orList.add(cb.like(root.get("province"), province + "%"));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			orList.clear();
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.fromString(types.get(0))));
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			predicateList.add(cb.equal(root.get("year"), year));
			predicateList.add(cb.equal(root.get("month"), month));
			Predicate all = cb.and(predicateList.toArray(new Predicate[0]));

			Predicate volExist = cb.equal(root.get("voltage"), voltage);

			return cb.and(all, volExist);
		};

		Specification<ElectricityTimeSlot> timeSlotSpecificationNoVol = (root, query, cb) -> {

			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			orList.add(cb.like(root.get("city"), city + "%"));
			orList.add(cb.like(root.get("province"), province + "%"));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			orList.clear();
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.fromString(types.get(0))));
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			predicateList.add(cb.equal(root.get("year"), year));
			predicateList.add(cb.equal(root.get("month"), month));
			Predicate all = cb.and(predicateList.toArray(new Predicate[0]));

			Predicate volIsNull = cb.isNull(root.get("voltage"));

			return cb.and(all, volIsNull);
		};

		Specification<ElectricityPrice> priceSpecification = (root, query, cb) -> {
			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			predicateList.add(cb.like(root.get("pk").get("city"), city + "%"));
			orList.add(cb.equal(root.get("pk").get("electricityCategory"), ElectricityCategoryEnum.fromString(types.get(0))));
			orList.add(cb.equal(root.get("pk").get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			predicateList.add(cb.like(root.get("pk").get("type"), types.get(1) + "%"));
			predicateList.add(cb.equal(root.get("pk").get("voltage"), voltage));
			predicateList.add(cb.equal(root.get("pk").get("year"), year));
			predicateList.add(cb.equal(root.get("pk").get("month"), month));
			return cb.and(predicateList.toArray(new Predicate[0]));
		};
		if (electricityTimeSlotRepository != null && electricityPriceRepository != null) {
			List<ElectricityTimeSlot> timeSlots = electricityTimeSlotRepository.findAll(timeSlotSpecification);
			if (timeSlots.isEmpty()) {
				timeSlots = electricityTimeSlotRepository.findAll(timeSlotSpecificationNoVol);
				if (timeSlots.isEmpty()) {
					log.error("get time info failed , please check the time data");
					throw new Exception("获取分时数据异常");
				}
			}
			List<ElectricityPrice> prices = electricityPriceRepository.findAll(priceSpecification);
			if (prices.isEmpty()) {
				log.error("get price failed , please check the price data!");
				throw new Exception("获取电数据异常");
			}
			timeSlots.forEach(o -> {

				ElectricityTimeCategoryEnum timeType = o.getTimeCategory();
				prices.forEach(price -> {
					ElectricityQueryResponse.priceData priceData = new ElectricityQueryResponse.priceData();
					priceData.setConsumption(price.getPk().getConsumption());
					switch (timeType) {
						case sharp:
							priceData.setPrice(price.getPriceSharp());
							break;
						case peek:
							priceData.setPrice(price.getPricePeek());
							break;
						case shoulder:
							priceData.setPrice(price.getPriceShoulder());
							break;
						case off_peek:
							priceData.setPrice(price.getPriceOffPeek());
							break;
						case ravine:
							priceData.setPrice(price.getPriceRavine());
							break;
					}
					priceData.setTimeCategory(timeType.getName());
					priceData.setStartDate(TimeUtil.toHmFormatStr(o.getStartTime()) + "~" + TimeUtil.toHmFormatStr(o.getEndTime()));
					response.getPriceList().add(priceData);
				});
			});
		} else {
			log.error("get repository failed");
		}
		response.getPriceList().sort(Comparator.comparing(ElectricityQueryResponse.priceData::getStartDate));
		return response;
	}

	public ElectricityQueryYearlyResponse getElectricityPriceLast12Month(String city, String type1, String type2,
	                                                                     String voltage1, String voltage2) throws Exception {
		ElectricityQueryYearlyResponse response = new ElectricityQueryYearlyResponse();
		response.setCity(city);
		response.setType_1(type1);
		response.setType_2(type2);
		response.setVoltage_1(voltage1);
		response.setVoltage_2(voltage2);
		ElectricityTimeSlotRepository electricityTimeSlotRepository = SpringBeanHelper.getBeanOrThrow(ElectricityTimeSlotRepository.class);
		ElectricityPriceRepository electricityPriceRepository = SpringBeanHelper.getBeanOrThrow(ElectricityPriceRepository.class);
		ProvinceRepository repository = SpringBeanHelper.getBeanOrThrow(ProvinceRepository.class);
		String province = repository.findProvinceByCity(city);
		Specification<ElectricityTimeSlot> timeSlotSpecification = (root, query, cb) -> {
			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			orList.add(cb.like(root.get("city"), city + "%"));
			orList.add(cb.like(root.get("province"), province + "%"));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			orList.clear();
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.fromString(type1)));
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			Predicate all = cb.and(predicateList.toArray(new Predicate[0]));
			Predicate volExist = cb.equal(root.get("voltage"), voltage1);
			return cb.and(all, volExist);
		};

		Specification<ElectricityTimeSlot> timeSlotSpecificationNoVol = (root, query, cb) -> {
			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			orList.add(cb.like(root.get("city"), city + "%"));
			orList.add(cb.like(root.get("province"), province + "%"));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			orList.clear();
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.fromString(type1)));
			orList.add(cb.equal(root.get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			Predicate all = cb.and(predicateList.toArray(new Predicate[0]));
			Predicate volIsNull = cb.isNull(root.get("voltage"));
			return cb.and(all, volIsNull);
		};
		Specification<ElectricityPrice> priceSpecification = (root, query, cb) -> {
			List<Predicate> predicateList = new ArrayList<>();
			List<Predicate> orList = new ArrayList<>();
			predicateList.add(cb.like(root.get("pk").get("city"), city + "%"));
			orList.add(cb.equal(root.get("pk").get("electricityCategory"), ElectricityCategoryEnum.fromString(type1)));
			orList.add(cb.equal(root.get("pk").get("electricityCategory"), ElectricityCategoryEnum.no_diff));
			predicateList.add(cb.or(orList.toArray(new Predicate[0])));
			predicateList.add(cb.like(root.get("pk").get("type"), type2));
			predicateList.add(cb.equal(root.get("pk").get("voltage"), voltage1));
			if (StringUtils.isNotEmpty(voltage2)) {
				predicateList.add(cb.equal(root.get("pk").get("consumption"), voltage2));
			}
			query.orderBy(cb.desc(root.get("pk").get("year")), cb.desc(root.get("pk").get("month")));
			return cb.and(predicateList.toArray(new Predicate[0]));
		};
		List<ElectricityTimeSlot> timeSlots = electricityTimeSlotRepository.findAll(timeSlotSpecification);
		if (timeSlots.isEmpty()) {
			timeSlots = electricityTimeSlotRepository.findAll(timeSlotSpecificationNoVol);
			if (timeSlots.isEmpty()) {
				log.error("get time info failed , please check the time data");
				throw new Exception("获取分时数据异常");
			}
		}
		List<ElectricityPrice> prices = electricityPriceRepository.findAll(priceSpecification, PageRequest.of(0, 12)).getContent();
		if (prices.isEmpty()) {
			log.error("get price failed , please check the price data!");
			throw new Exception("获取电价数据异常");
		}
		Map<Integer, List<ElectricityTimeSlot>> timeSlotMap = timeSlots.stream().collect(Collectors.groupingBy(ElectricityTimeSlot::getMonth));
		// 时段划分取最新一年的数据
		Map<Integer, List<ElectricityTimeSlot>> deduplicatedMap = timeSlotMap.entrySet().stream().collect(Collectors.toMap(
				Map.Entry::getKey,
				entry -> {
					List<ElectricityTimeSlot> sortedList = entry.getValue().stream()
							.sorted(Comparator.comparingInt(ElectricityTimeSlot::getYear).reversed())
							.collect(Collectors.toList());
					int maxYear = sortedList.get(0).getYear();
					return sortedList.stream()
							.filter(slot -> slot.getYear() == maxYear)
							.collect(Collectors.toList());
				}
		));
		Map<Integer, ElectricityPrice> priceMap = prices.stream().collect(Collectors.toMap(o -> (o.getPk().getMonth()), (o -> o)));
		for (Map.Entry<Integer, List<ElectricityTimeSlot>> entry : deduplicatedMap.entrySet()) {
			ElectricityQueryYearlyResponse.priceInfo priceInfo = new ElectricityQueryYearlyResponse.priceInfo();
			priceInfo.setMonth(String.valueOf(entry.getKey()));
			entry.getValue().forEach(o -> {
				ElectricityTimeCategoryEnum timeType = o.getTimeCategory();
				ElectricityPrice price = priceMap.get(entry.getKey());
				if (price == null) {
					return;
				}

				int count = (int) ((o.getEndTime().getTimeInMillis() - o.getStartTime().getTimeInMillis()) / (15 * 60 * 1000));
				if (count < 0) {
					count = 24 * 4 + count;
				}
				Calendar st = Calendar.getInstance();
				st.setTime(o.getStartTime().getTime());
				Calendar et = Calendar.getInstance();
				et.setTime(o.getStartTime().getTime());
				et.add(Calendar.MINUTE, 15);
				for (int i = 0; i < count; i++) {
					ElectricityQueryYearlyResponse.priceData data = new ElectricityQueryYearlyResponse.priceData();
					switch (timeType) {
						case sharp:
							data.setPrice(price.getPriceSharp() == null ? price.getPricePeek() : price.getPriceSharp());
							break;
						case peek:
							data.setPrice(price.getPricePeek());
							break;
						case shoulder:
							data.setPrice(price.getPriceShoulder() == null ? price.getPricePeek() : price.getPriceShoulder());
							break;
						case off_peek:
							data.setPrice(price.getPriceOffPeek());
							break;
						case ravine:
							data.setPrice(price.getPriceRavine() == null ? price.getPriceOffPeek() : price.getPriceRavine());
							break;
					}
					data.setTimeCategory(timeType.getName());
					data.setPeriod(TimeUtil.toHmFormatStr(st) + "~" + TimeUtil.toHmFormatStr(et));
					st.add(Calendar.MINUTE, 15);
					et.add(Calendar.MINUTE, 15);
					priceInfo.getPrices().add(data);
				}

			});
			priceInfo.getPrices().sort(Comparator.comparing(ElectricityQueryYearlyResponse.priceData::getPeriod));
			response.getPricesList().add(priceInfo);
		}
		return response;
	}

	public List<EleNodeInfo> queryCityElectricitySystem() {
		ElectricityPriceRepository electricityPriceRepository = SpringBeanHelper.getBeanOrThrow(ElectricityPriceRepository.class);
		List<Object[]> infos = electricityPriceRepository.findAllCityInfos();
		EleNodeInfo root = new EleNodeInfo();
		for (Object[] info : infos) {
			EleNodeInfo currNode = root;
			for (int i = 0; i < info.length; i++) {
				String field = TYPE_LIST[i];
				String value;
				if (info[i] instanceof Integer) {
					value = ElectricityCategoryEnum.fromCodeGetName((Integer) info[i]);
				} else {
					value = String.valueOf(info[i]);
				}
				if (value.equals("-")) {
					break;
				}

				EleNodeInfo child;
				if (currNode.getChildrenMap().containsKey(value)) {
					child = currNode.getChildrenMap().get(value);
				} else {
					child = new EleNodeInfo(field, value);
					currNode.addEleNodeInfo(child);
				}

				currNode = child;
			}
		}
		return root.getChildren();
	}
}
